package mqtt

import (
	"context"
	"fmt"
	"time"

	"iot-cloud-platform/internal/logger"
	"iot-cloud-platform/internal/models"

	"github.com/google/uuid"
)

// DeviceAuthenticator handles device authentication for MQTT connections
type DeviceAuthenticator struct {
	mqttManager   *MQTTManager
	authCallbacks map[string]AuthCallback
}

// AuthCallback defines the callback function for authentication events
type AuthCallback func(deviceID uuid.UUID, event string, success bool) error

// AuthEvent represents an authentication event
type AuthEvent struct {
	DeviceID   uuid.UUID `json:"device_id"`
	ProductKey string    `json:"product_key"`
	DeviceName string    `json:"device_name"`
	ClientID   string    `json:"client_id"`
	Username   string    `json:"username"`
	IPAddress  string    `json:"ip_address"`
	Timestamp  time.Time `json:"timestamp"`
	Success    bool      `json:"success"`
	Reason     string    `json:"reason,omitempty"`
}

// NewDeviceAuthenticator creates a new device authenticator
func NewDeviceAuthenticator(mqttManager *MQTTManager) *DeviceAuthenticator {
	return &DeviceAuthenticator{
		mqttManager:   mqttManager,
		authCallbacks: make(map[string]AuthCallback),
	}
}

// HandleDeviceAuth processes device authentication requests
func (da *DeviceAuthenticator) HandleDeviceAuth(ctx context.Context, username, password, clientID string) bool {
	// Parse username to extract product key and device name
	// Format: productKey/deviceName
	productKey, deviceName, err := da.parseUsername(username)
	if err != nil {
		logger.GetLogger().WithError(err).Error("Failed to parse username")
		da.logAuthEvent("", productKey, deviceName, clientID, "", false, "Invalid username format")
		return false
	}

	// Validate device credentials
	device, err := da.mqttManager.deviceService.ValidateDeviceCredentials(ctx, productKey, deviceName, password)
	if err != nil {
		logger.GetLogger().WithError(err).Error("Device authentication failed")
		da.logAuthEvent("", productKey, deviceName, clientID, "", false, "Invalid credentials")
		return false
	}

	// Check if device is active
	if device.Status == models.DeviceStatusDisabled {
		logger.GetLogger().Warn("Disabled device attempted to connect")
		da.logAuthEvent(device.ID.String(), productKey, deviceName, clientID, "", false, "Device disabled")
		return false
	}

	// Authentication successful
	logger.GetLogger().Infof("Device %s authenticated successfully", device.ID)
	da.logAuthEvent(device.ID.String(), productKey, deviceName, clientID, "", true, "")

	// Set device online
	da.mqttManager.deviceService.SetDeviceOnline(ctx, device.ID)

	// Execute auth callbacks
	for _, callback := range da.authCallbacks {
		if err := callback(device.ID, "connect", true); err != nil {
			logger.GetLogger().WithError(err).Error("Auth callback error")
		}
	}

	return true
}

// HandleDeviceDisconnect processes device disconnection events
func (da *DeviceAuthenticator) HandleDeviceDisconnect(ctx context.Context, clientID string) {
	// Extract device ID from client ID or track it separately
	// For simplicity, we'll log the disconnection
	logger.GetLogger().Infof("Device with client ID %s disconnected", clientID)

	// In a real implementation, you'd track which device this client ID belongs to
	// and mark it as offline
}

// RegisterAuthCallback registers a callback for authentication events
func (da *DeviceAuthenticator) RegisterAuthCallback(event string, callback AuthCallback) {
	da.authCallbacks[event] = callback
}

// parseUsername parses the MQTT username to extract product key and device name
func (da *DeviceAuthenticator) parseUsername(username string) (string, string, error) {
	// Implement username parsing logic
	// This depends on your authentication scheme
	// For example: "productKey/deviceName" or just "deviceName"

	// Simplified implementation
	if len(username) < 3 {
		return "", "", fmt.Errorf("username too short")
	}

	// You would implement actual parsing logic here
	return "default_product", username, nil
}

// logAuthEvent logs authentication events
func (da *DeviceAuthenticator) logAuthEvent(deviceID, productKey, deviceName, clientID, ipAddress string, success bool, reason string) {
	event := AuthEvent{
		ProductKey: productKey,
		DeviceName: deviceName,
		ClientID:   clientID,
		IPAddress:  ipAddress,
		Timestamp:  time.Now(),
		Success:    success,
		Reason:     reason,
	}

	if deviceID != "" {
		if id, err := uuid.Parse(deviceID); err == nil {
			event.DeviceID = id
		}
	}

	// Log to database or external system
	eventLog := models.EventLog{
		Type:   models.EventTypeDevice,
		Action: "mqtt_auth",
		Details: map[string]interface{}{
			"auth_event": event,
		},
		Timestamp: event.Timestamp,
	}

	if event.DeviceID != uuid.Nil {
		eventLog.DeviceID = &event.DeviceID
	}

	if err := da.mqttManager.db.Create(&eventLog).Error; err != nil {
		logger.GetLogger().WithError(err).Error("Failed to save auth event")
	}
}

// DeviceConnectionManager manages device connections and heartbeats
type DeviceConnectionManager struct {
	mqttManager       *MQTTManager
	connectedDevices  map[uuid.UUID]*DeviceConnection
	heartbeatInterval time.Duration
}

// DeviceConnection represents an active device connection
type DeviceConnection struct {
	DeviceID      uuid.UUID
	ClientID      string
	ConnectedAt   time.Time
	LastSeen      time.Time
	HeartbeatChan chan bool
}

// NewDeviceConnectionManager creates a new connection manager
func NewDeviceConnectionManager(mqttManager *MQTTManager) *DeviceConnectionManager {
	return &DeviceConnectionManager{
		mqttManager:       mqttManager,
		connectedDevices:  make(map[uuid.UUID]*DeviceConnection),
		heartbeatInterval: 30 * time.Second,
	}
}

// AddConnection adds a new device connection
func (dcm *DeviceConnectionManager) AddConnection(deviceID uuid.UUID, clientID string) {
	connection := &DeviceConnection{
		DeviceID:      deviceID,
		ClientID:      clientID,
		ConnectedAt:   time.Now(),
		LastSeen:      time.Now(),
		HeartbeatChan: make(chan bool, 1),
	}

	dcm.connectedDevices[deviceID] = connection
	logger.GetLogger().Infof("Added connection for device %s", deviceID)

	// Start heartbeat monitoring for this device
	go dcm.monitorHeartbeat(connection)
}

// RemoveConnection removes a device connection
func (dcm *DeviceConnectionManager) RemoveConnection(deviceID uuid.UUID) {
	if connection, exists := dcm.connectedDevices[deviceID]; exists {
		close(connection.HeartbeatChan)
		delete(dcm.connectedDevices, deviceID)

		// Set device offline
		dcm.mqttManager.deviceService.SetDeviceOffline(context.Background(), deviceID)
		logger.GetLogger().Infof("Removed connection for device %s", deviceID)
	}
}

// UpdateLastSeen updates the last seen timestamp for a device
func (dcm *DeviceConnectionManager) UpdateLastSeen(deviceID uuid.UUID) {
	if connection, exists := dcm.connectedDevices[deviceID]; exists {
		connection.LastSeen = time.Now()

		// Send heartbeat signal
		select {
		case connection.HeartbeatChan <- true:
		default:
		}
	}
}

// monitorHeartbeat monitors device heartbeat and marks offline if no activity
func (dcm *DeviceConnectionManager) monitorHeartbeat(connection *DeviceConnection) {
	ticker := time.NewTicker(dcm.heartbeatInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			// Check if device has been inactive for too long
			if time.Since(connection.LastSeen) > dcm.heartbeatInterval*2 {
				logger.GetLogger().Warnf("Device %s heartbeat timeout", connection.DeviceID)
				dcm.mqttManager.deviceService.SetDeviceOffline(context.Background(), connection.DeviceID)
				return
			}
		case <-connection.HeartbeatChan:
			// Received heartbeat, continue monitoring
		case stop := <-connection.HeartbeatChan:
			if !stop {
				return // Channel closed, stop monitoring
			}
		}
	}
}

// GetConnectedDevices returns the list of currently connected devices
func (dcm *DeviceConnectionManager) GetConnectedDevices() []uuid.UUID {
	devices := make([]uuid.UUID, 0, len(dcm.connectedDevices))
	for deviceID := range dcm.connectedDevices {
		devices = append(devices, deviceID)
	}
	return devices
}

// GetConnectionCount returns the number of connected devices
func (dcm *DeviceConnectionManager) GetConnectionCount() int {
	return len(dcm.connectedDevices)
}
